package com.dituhui.gps.local;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.math3.ml.clustering.Clusterable;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import com.dituhui.gps.service.GetTrack;
import com.dituhui.gps.vo.GPSLine;
import com.dituhui.gps.vo.GPSPoint;
import com.dituhui.tool.algorithm.ClusterTool;
import com.dituhui.tool.algorithm.ClusterTool.Target;
import com.dituhui.tool.gis.CoordConverter;
import com.dituhui.tool.io.Constants;
import com.dituhui.tool.io.ESTool;
import com.dituhui.tool.util.DateUtil;
import com.dituhui.tool.util.TimedJob;

/**
 * ES数据每日定制转换任务。将ES实时数据转到ES历史。
 * 
 * @author liweigu
 * 
 */
public class ESDataJob extends TimedJob {
	private static String RealtimeESUrl = Constants.getValue("RealtimeESUrl", "gps_config.properties");
	private static String HistoryESUrl = Constants.getValue("HistoryESUrl", "gps_config.properties");
	private static String HistoryES_IndexPrefix = Constants.getValue("HistoryES_IndexPrefix", "gps_config.properties");
	protected static Logger LOGGER = Logger.getLogger(ESDataJob.class);

	// ES历史的字段名
	private static String INFOS_FIELD = "infos";
	private static String PlateNumber_FIELD = "plateNumber";
	private static String AccountID_FIELD = "accountID";
	// private static String GroupId = "groupId";

	@Override
	/**
	 * 执行任务。
	 */
	public void execute(JobExecutionContext context) throws JobExecutionException {
		LOGGER.info("in ESDataJob.execute(JobExecutionContext context).");
		// ES实时数据的日期
		String realtimeESDate = Constants.getValue("RealtimeESDate", "gps_config.properties");
		if (realtimeESDate == null || realtimeESDate.length() == 0) {
			// 默认读取昨天的数据
			Date today = new Date();
			Date yesterday = DateUtil.getPreviousDate(today);
			realtimeESDate = DateUtil.getDayFormat().format(yesterday);
		}
		LOGGER.info("realtimeESDate=" + realtimeESDate);
		try {
			HashMap<String, GPSLine> gpsLines = GetTrack.getGPSLineFromRealtimeES(realtimeESDate, RealtimeESUrl);
			if (gpsLines == null || gpsLines.size() == 0) {
				LOGGER.info("gpsLines为空");
				String accountId = "emptyAccount";
				// String groupId = null;
				String plateNumber = "";
				String handledCoordForSave = "emptyContent";
				String deletedCoordForCar = "";
				saveCoord(accountId, plateNumber, handledCoordForSave, deletedCoordForCar, realtimeESDate);
			} else {
				for (String accountId : gpsLines.keySet()) {
					GPSLine gpsLine = gpsLines.get(accountId);
					// 根据车辆ID获取坐标集合
					List<GPSPoint> originalGPSPoints = gpsLine.getGPSPoints();
					// String groupId = gpsLine.getGroupId();
					if (originalGPSPoints.size() > 0) {
						// 去重，因为当天数据里有大量重发上传的GPS数据，非常影响性能。
						GPSLine.distinctAndFilterByGPSTimeToDeleteTag(originalGPSPoints, realtimeESDate);
						// System.out.println("originalGPSPoints.size()=" + (originalGPSPoints.size()));

						boolean deleted = false;
						// 未删除的点
						List<GPSPoint> undeletedGPSPoints = GPSLine.filterByDeleteTag(originalGPSPoints, deleted);
						// System.out.println("undeletedGPSPoints=" + (undeletedGPSPoints==null?null:undeletedGPSPoints.size()));
						// 删除的点
						deleted = true;
						List<GPSPoint> deletedGPSPoints = GPSLine.filterByDeleteTag(originalGPSPoints, deleted);

						// 排序。给去重后的排序，而不是原生坐标。
						boolean flag = GPSLine.sortByTime(undeletedGPSPoints);
						if (!flag) {
							// 存在GPSTime为空的点
						}

						// 处理飞点。给去重后的处理飞点，而不是原生坐标。
						List<GPSPoint> handledGPSPoints = FlySpotHandler.dealFlySpot(undeletedGPSPoints);

						// 转换坐标格式
						GPSLine handledGPSLine = new GPSLine();
						handledGPSLine.setGPSPoints(handledGPSPoints);
						String handledCoordForSave = handledGPSLine.toCSVString();
						GPSLine deletedGPSLine = new GPSLine();
						deletedGPSLine.setGPSPoints(deletedGPSPoints);
						String deletedCoordForCar = deletedGPSLine.toCSVString();
						// 获取车牌号
						String plateNumber = null;
						if (handledGPSPoints.size() > 0) {
							plateNumber = handledGPSPoints.get(0).getPlateNumber();
							if (plateNumber != null && plateNumber.length() > 0) {
								if (accountId != null && accountId.length() > 0) {
									// 保存到历史库
									if (saveCoord(accountId, plateNumber, handledCoordForSave, deletedCoordForCar, realtimeESDate)) {
										// if (true) { // 测试用
										LOGGER.info("车辆ID:" + accountId + "_坐标导出成功");
										// 如果保存成功则删除ES实时中的车辆数据
										if (delCarCoord(realtimeESDate)) {
											LOGGER.info("车辆ID:" + accountId + "_坐标删除成功");
										} else {
											LOGGER.warn("车辆ID:" + accountId + "_坐标删除失败");
										}
									} else {
										LOGGER.warn("车辆ID:" + accountId + "_坐标导出失败");
									}
								} else {
									LOGGER.info("accountId为空");
								}
							} else {
								LOGGER.info("plateNumber为空");
							}
						} else {
							LOGGER.info("handledGPSPoints.size() == 0");
						}
					} else {
						LOGGER.info("originalList.size() == 0");
					}
				}
			}
		} catch (Exception e) {
			LOGGER.error("坐标点处理异常", e);
		}
	}

	/**
	 * 将处理后的坐标保存到历史库
	 * 
	 * @param undeletedCoords 未删除的车辆坐标
	 * @param deletedCoords 已删除的车辆坐标
	 * @return
	 */
	private boolean saveCoord(String accountID, String plateNumber, String undeletedCoords, String deletedCoords, String date) {
		// 将已删除的车辆坐标单独存储字段，避免影响正常数据的查询性能
		String[] keys;
		// if (groupId == null) {
		keys = new String[] { AccountID_FIELD, PlateNumber_FIELD, INFOS_FIELD };
		// } else {
		// keys = new String[] { AccountID, PlateNumber, INFOS, GroupId };
		// }
		String index = HistoryES_IndexPrefix + date;
		String deletedIndex = HistoryES_IndexPrefix + date + "_del";
		// ES历史的车辆轨迹的routing为plateNumber的值
		String routing = plateNumber;
		// 调用egisptool的写入es方法
		if (undeletedCoords != null && undeletedCoords.length() > 0) {
			String[] undeletedValues;
			// if (groupId == null) {
			undeletedValues = new String[] { accountID, plateNumber, undeletedCoords };
			// } else {
			// undeletedValues = new String[] { accountID, plateNumber, undeletedCoords, groupId };
			// }
			ESTool.write(index, routing, keys, undeletedValues, HistoryESUrl);
		}
		if (deletedCoords != null && deletedCoords.length() > 0) {
			String[] deletedValues;
			// if (groupId == null) {
			deletedValues = new String[] { accountID, plateNumber, deletedCoords };
			// } else {
			// deletedValues = new String[] { accountID, plateNumber, deletedCoords, groupId };
			// }
			ESTool.write(deletedIndex, routing, keys, deletedValues, HistoryESUrl);
		}
		return true;
	}

	/**
	 * 删除指定车辆坐标数据
	 * 
	 * @param carID 车辆ID
	 * @return 删除是否成功
	 */
	private boolean delCarCoord(String date) {
		// 删除
		// String index = "gps_" + date;
		// ESTool.deleteIndex(index, RealtimeESUrl);
		// return true;
		return false;
	}

	private static class FlySpotHandler {
		// 按多少个数拆分集合
		private static int splitCount = 10000; // 设值超过一天的最大数量，不做拆分计算，因为拆分会影响停滞点的计算
		// 处理飞点的半径，200米。
		public static double radius = CoordConverter.GPS_DELTA_PER_KM * 0.2;
		// 聚类组的最少成员数量
		public static int minPointCount = 10;
		private static double MAX_DIS = 1;
		public static int NormalGpsTag = 0;
		public static int StopGpsTag = 1;

		/**
		 * 飞点处理
		 * 
		 * @param originalList 待处理坐标
		 * @return
		 */
		public static List<GPSPoint> dealFlySpot(List<GPSPoint> originalList) {
			List<GPSPoint> coordList = new ArrayList<GPSPoint>(originalList);

			List<GPSPoint> results = new ArrayList<GPSPoint>();
			int listCount = coordList.size();
			// 拆分集合
			if (listCount <= splitCount) {
				// 如果待处理数小于拆分数，则直接处理
				results = FlySpotHandler.dealCluster(coordList, radius, minPointCount);
			} else {
				// 计算需要拆分处理的次数
				int repeatCount = 0;
				// 余数
				int modCount = listCount % splitCount;
				if (modCount == 0) {
					repeatCount = listCount / splitCount;
				} else {
					repeatCount = listCount / splitCount + 1;
				}
				// 对集合进行拆分处理
				for (int i = 0; i < repeatCount; i++) {
					List<GPSPoint> subList;

					if (i != (repeatCount - 1)) {
						// 非最后一批
						subList = new ArrayList<GPSPoint>(coordList.subList(i * splitCount, (i + 1) * splitCount));
					} else {
						// 最后一批
						subList = new ArrayList<GPSPoint>(coordList.subList(i * splitCount, i * splitCount + modCount));
					}

					List<GPSPoint> subResults = FlySpotHandler.dealCluster(subList, radius, minPointCount);
					for (GPSPoint cInfo : subResults) {
						// 添加汇总的集合
						results.add(cInfo);
					}
				}
			}

			return results;
		}

		/**
		 * 聚类处理
		 * 
		 * @param coordList 待处理坐标列表
		 * @return 聚类分析结果
		 */
		public static List<GPSPoint> dealCluster(List<GPSPoint> coordList, double radius, int minPointCount) {
			// 转换为待处理对象
			List<Clusterable> targets = FlySpotHandler.buildTarget(coordList);
			// 聚类处理
			Map<Target, List<Target>> clusterPoints = ClusterTool.dbSCANCluster(targets, radius, minPointCount);
			// 组装返回结果
			FlySpotHandler.setGPSTag(clusterPoints, coordList);
			return coordList;
		}

		/**
		 * 构建聚类分析的结果，移除停留点（同一组停留点只保留前后2个），保留未分类的点
		 * 
		 * @param clusterPoints 聚类分析结果
		 * @param originGpsPoints 原始坐标，方法会改变其Point的gpsTag属性。会
		 */
		public static void setGPSTag(Map<Target, List<Target>> clusterPoints, List<GPSPoint> originGpsPoints) {
			for (Clusterable keyTarget : clusterPoints.keySet()) {
				List<Target> targetList = clusterPoints.get(keyTarget);

				// int j = 0;
				for (int i = 0; i < targetList.size(); i++) {
					Target target = targetList.get(i);
					// 设置order
					if (target.getProperties().size() > 1) {
						double order = target.getProperties().get(1);
						// 遍历originGpsPoints设置gpsTag
						// TODO: 只顺序向前遍历，找到后跳出循环，下一次继续遍历
						for (int j = 0; j < originGpsPoints.size(); j++) {
							GPSPoint gpsPoint = originGpsPoints.get(j);
							// 根据order值确定给哪一个gpsPoint赋值
							if (gpsPoint.getOrder() == order) {
								// if(gpsPoint.getGpsTag() != 0){
								// System.out.println("gpsTag=" + gpsTag + ",order=" +
								// order);
								// }
								gpsPoint.setGpsTag(StopGpsTag);
								break;
							}
						}
					}
				}
			}
		}

		/**
		 * 根据坐标构建聚类Target对象
		 * 
		 * @param gpsPoints 坐标列表
		 * @return 聚类Target对象列表
		 */
		public static List<Clusterable> buildTarget(List<GPSPoint> gpsPoints) {
			List<Clusterable> targets = new ArrayList<Clusterable>();

			int order = 0;
			for (GPSPoint gpsPoint : gpsPoints) {
				Target target = new Target();
				// LOGGER.debug("TIME:"+coord.getTime()+",X:"+coord.getX()+",Y:"+coord.getY());
				// 设置point
				List<Double> point = new ArrayList<Double>();
				point.add(gpsPoint.getX() / MAX_DIS);
				point.add(gpsPoint.getY() / MAX_DIS);
				target.setPoint(point);
				// 设置properties
				List<Double> properties = new ArrayList<Double>();
				// 设置weigth
				if (gpsPoint.getGPSTime() != null) {
					properties.add(gpsPoint.getGPSTime().getTime() / MAX_DIS);
				} else {
					properties.add(null);
				}
				// 设置order
				// 给target和gpsPoint设置相同顺序的order数字
				properties.add((double) order);
				target.setProperties(properties);
				gpsPoint.setOrder(order);
				if (gpsPoint.getGpsTag() == StopGpsTag) {
					// 在准备数据过程中，重置停滞点标签为普通标签
					gpsPoint.setGpsTag(NormalGpsTag);
				}
				++order;
				// 添加到集合
				targets.add(target);
			}

			return targets;
		}
	}
}
